package com.ludomancy.sos64;

import java.awt.Graphics;

import com.ludomancy.gamelib.ImageStrip;
import com.ludomancy.gamelib.Tileset;

public class Terrain extends Tileset
{
	private Accumulator current = new Accumulator(300);
	private int currentSide = 0;
	public int[][] depth;
	public int[][] type;
	public int[][] breakable;
	public int[][] detail;
	
	public static final int TERRAIN_TYPE_DRY = 0;
	public static final int TERRAIN_TYPE_WATER = 1;
	public static final int TERRAIN_TYPE_SHORE = 2;
	public static final int TERRAIN_TYPE_CLIFF = 3;
	
	public static final int TERRAIN_UNBREAKABLE = 0;
	public static final int TERRAIN_BREAKABLE = 1;
	
	public static final int DEPTH_LOW = 0;
	public static final int DEPTH_HIGH = 1;
		
	public Terrain(String file, ImageStrip tiles)
	{
		super(file, tiles);
		depth = new int[width][height];
		type = new int[width][height];
		breakable = new int[width][height];
		detail = new int[width][height];
		for (int y = 0; y < height; y++)
		{
			for (int x = 0; x < width; x++)
			{
				if(map[x][y] == TILE_WATER)
				{
					depth[x][y] = DEPTH_LOW;
					type[x][y] = TERRAIN_TYPE_WATER;
				}
				else
				{
					switch(map[x][y])
					{
						case TILE_BREAKABLE:
							depth[x][y] = DEPTH_HIGH;
							type[x][y] = TERRAIN_TYPE_DRY;
							breakable[x][y] = TERRAIN_BREAKABLE;
							break;
						case TILE_SOLID:
						case TILE_SOLID2:
						case TILE_SOLID3:
							detail[x][y] = map[x][y]; 
							depth[x][y] = DEPTH_HIGH;
							type[x][y] = TERRAIN_TYPE_DRY;
							break;
						case TILE_WATER:
							depth[x][y] = DEPTH_LOW;
							type[x][y] = TERRAIN_TYPE_WATER;
							break;
						case TILE_LOW:
							depth[x][y] = DEPTH_LOW;
							type[x][y] = TERRAIN_TYPE_DRY;
							break;
					}
				}
			}
		}
	}
	
	public boolean isBreakable(int x, int y)
	{
		return breakable[x][y] == TERRAIN_BREAKABLE;
	}
	
	public boolean isWater(int x, int y)
	{
		return type[x][y] == TERRAIN_TYPE_WATER;
	}
	
	public boolean isHigh(int x, int y)
	{
		return depth[x][y] == DEPTH_HIGH;
	}
	
	public boolean isLow(int x, int y)
	{
		return !isHigh(x, y);
	}
	
	public boolean isShore(int x, int y)
	{
		return type[x][y] == TERRAIN_TYPE_SHORE;
	}
	
	public boolean isShoreOrCliff(int x, int y)
	{
		return type[x][y] == TERRAIN_TYPE_CLIFF || type[x][y] == TERRAIN_TYPE_SHORE; 
	}
	
	public void updateTerrain()
	{
		flood();
		resetTerrain();
		for (int i = TRANSITIONS.length-1; i >= 0; i--)
		{
			updateTerrainForTransition(i);
		}
	}
	
	private void updateTerrainForTransition(int trans)
	{
		for (int y = 0; y < height; y++)
		{
			for (int x = 0; x < width; x++)
			{
				updateTerrainTile(x, y, trans);
			}
		}
	}
	
	public void updateTerrainTile(int sx, int sy, int trans)
	{
		int[][] transition = TRANSITIONS[trans];
		if((sy + transition.length - 1) >= height || (sx + transition[0].length - 1) >= width)
		{
			return;
		}
		
		if(transitionMatches(sx, sy, trans))
		{
			for (int y = 0; y < transition.length; y++)
			{
				for (int x = 0; x < transition[0].length; x++)
				{
					if(TERRAIN_RESULTS[trans][y][x] != -1)
					{
						map[sx + x][sy + y] = getTileForTransition(sx+x, sy+y, trans);
					}
				}
			}
		}
	}
	
	private boolean transitionMatches(int sx, int sy, int trans)
	{
		int[][] transition = TRANSITIONS[trans];
		boolean matches = true;
		
		for (int y = 0; y < transition.length; y++)
		{
			for (int x = 0; x < transition[0].length; x++)
			{
				
				if(depth[sx+x][sy+y] != transition[y][x])
				{
					matches = false;
				}
			}
		}
		return matches;
	}
	
	private int getTileForTransition(int x, int y, int trans)
	{
		if(isShore(x,y))
		{
			return WATER_TRANSITION_TILES[trans];
		}
		else
		{
			return DRY_TRANSITION_TILES[trans];
		}
	}
	
	private void resetTerrain()
	{
		for (int y = 0; y < height; y++)
		{
			for (int x = 0; x < width; x++)
			{
				map[x][y] = depth[x][y] == DEPTH_LOW ? TILE_LOW : (detail[x][y]);
				if(type[x][y] == TERRAIN_TYPE_WATER)
				{
					map[x][y] = TILE_WATER;
				}
				if(depth[x][y] == DEPTH_HIGH && breakable[x][y] == TERRAIN_BREAKABLE)
				{
					map[x][y] = TILE_BREAKABLE;
				}
			}
		}
	}
	
	private void flood()
	{
		for (int y = 0; y < height; y++)
		{
			for (int x = 0; x < width; x++)
			{
				if(type[x][y] == TERRAIN_TYPE_WATER)
				{
					floodTile(x,y, true, true);
				}
				else
				if(type[x][y] == TERRAIN_TYPE_DRY)
				{
					floodTile(x,y, true, false);
				}
			}
		}
	}
	
	public void update()
	{
		current.update();
		if(current.isDone())
		{
			currentSide = (currentSide+1) % 3;
			current.reset();
		}
	}
	
	protected void paintTile(Graphics g, int x, int y, int tile)
	{
		if(tile == TILE_WATER && Game.game.dam.isConsumed())
		{
			int tileID = TILE_WATER;
			switch(currentSide)
			{
				case 0:
					tileID = TILE_WATER;
					break;
				case 1:
					tileID = TILE_WATER2;
					break;
				case 2:
					tileID = TILE_WATER3;
					break;
			}
			super.paintTile(g, x, y, tileID);
		}
		else
		{
			super.paintTile(g, x, y, tile);
		}
	}
	
	private void floodTile(int x, int y, boolean entryPoint, boolean water)
	{
		if(isIn(x,y))
		{
			if(depth[x][y] == DEPTH_LOW)
			{
				if(entryPoint || (water ? type[x][y] != TERRAIN_TYPE_WATER : type[x][y] != TERRAIN_TYPE_DRY))
				{
					type[x][y] = water ? TERRAIN_TYPE_WATER : TERRAIN_TYPE_DRY;
					floodTile(x+1,y, false, water);
					floodTile(x-1,y, false, water);
					floodTile(x,y+1, false, water);
					floodTile(x,y-1, false, water);
					floodTile(x+1,y+1, false, water);
					floodTile(x-1,y-1, false, water);
					floodTile(x+1,y-1, false, water);
					floodTile(x-1,y+1, false, water);
				}
			}
			else
			{
				type[x][y] = water ? TERRAIN_TYPE_SHORE : TERRAIN_TYPE_CLIFF;
			}
		}
	}
			
	private static final int[][][] TRANSITIONS = new int[][][]
 	{
 		{
 			{1, 0},
 			{0, 0},
 		},
 		{ 
 			{0, 1},
 			{0, 0},
 		},
 		{ 
 			{0, 0},
 			{0, 1},
 		},
 		{ 
 			{0, 0},
 			{1, 0},
 		},
 		{
 			{0, 1},
 			{1, 1},
 		},
 		{ 
 			{1, 0},
 			{1, 1},
 		},
 		{ 
 			{1, 1},
 			{1, 0},
 		},
 		{ 
 			{1, 1},
 			{0, 1},
 		},
 		{ 
 			{1},
 			{0},
 		},
 		{ 
 			{0,1},
 		},
 		{ 
 			{0},
 			{1},
 		},
 		{ 
 			{1,0},
 		},
 	};
	
	private static final int CORNER_NW = 0;
	private static final int CORNER_NE = 1;
	private static final int CORNER_SE = 2;
	private static final int CORNER_SW = 3;
	private static final int CORNER_INNER_NW = 4;
	private static final int CORNER_INNER_NE = 5;
	private static final int CORNER_INNER_SE = 6;
	private static final int CORNER_INNER_SW = 7;
	private static final int WALL_N  = 8;
	private static final int WALL_W  = 9;
	private static final int WALL_S  = 10;
	private static final int WALL_E  = 11;
	
	private static final int TILE_SOLID = 6;
	private static final int TILE_SOLID2 = 19;
	private static final int TILE_SOLID3 = 11;
	private static final int TILE_BREAKABLE = 15;
	private static final int TILE_WATER = 13;
	private static final int TILE_WATER2 = 16;
	private static final int TILE_WATER3 = 17;
	private static final int TILE_LOW = 14;
	private int[] DRY_TRANSITION_TILES = new int[]{12,10,0,2,3,4,9,8,11,5,1,7};
	private int[] WATER_TRANSITION_TILES = DRY_TRANSITION_TILES;

	private static final int[][][] TERRAIN_RESULTS = new int[][][]
 	{
 		{
 			{CORNER_NW,-1},
 			{-1,-1},
 		},
 		{
 			{-1,CORNER_NE},
 			{-1,-1},
 		},
 		{
 			{-1,-1},
 			{-1,CORNER_SE},
 		},
 		{
 			{-1,-1},
 			{CORNER_SW,-1},
 		},

 		{
 			{-1,-1},
 			{-1,CORNER_INNER_SE},
 		},
 		{
 			{-1,-1},
 			{CORNER_INNER_SW,-1},
 		},
 		{
 			{CORNER_INNER_NW,-1},
 			{-1,-1},
 		},
 		{
 			{-1,CORNER_INNER_NE},
 			{-1,-1},
 		},

 		
 		{
 			{WALL_N},
 			{-1},
 		},
 		{
 			{-1,WALL_E},
 		},
 		{
 			{-1},
 			{WALL_S},
 		},
 		{
 			{WALL_W, -1},
 		},
 	};

	public static final int TILE_POISON = 29;
	public void chainBreak()
	{
		for (int y = 0; y < height; y++)
		{
			for (int x = 0; x < width; x++)
			{
				if(isBreakable(x, y))
				{
					depth[x][y] = DEPTH_LOW;
					breakable[x][y] = TERRAIN_UNBREAKABLE;
				}
			}
		}
		updateTerrain();
	}
}

